package hk.com.fgoproduction.getdroplets.Lib.TreeList

import com.dropbox.core.v2.files.FolderMetadata
import com.dropbox.core.v2.files.Metadata
import hk.com.fgoproduction.getdroplets.Lib.Common.ToolInstance.LogInstance
import hk.com.fgoproduction.getdroplets.Lib.FileMan.FileMan
import java.io.File
import java.util.*

/***
 * This list just store the parent and children nodes, for further deeper structure use children treelist as new parent.
 */
class TreeList() {

    private val showLog = false

    private var rootNode = TreeNode()                               //point to root node(parent node)
    private var children = LinkedHashMap<String, TreeList>()        //<path, node>

    var isInitialized = false
        private set

    constructor(root: TreeNode) : this() {
        setRoot(root)
    }

    /**
     * To check the list rootNode is the root of the structure or not
     *
     * Default:
     * local - "/mnt/sdcard" or "/storage/{ADAPTIVE STORAGE}"
     * dropbox - ""
     * @return true if rootNode is root of whole structure, else false
     */
    fun isRoot(): Boolean {
        return rootNode.level == TreeNode.LEVEL_TOP
    }

    /**
     * Get-Set Method
     */

    fun getRootPath(): String {
        return rootNode.path
    }

    fun getRootLevel(): Int {
        return rootNode.level
    }

    fun setRoot(root: TreeNode) {
        rootNode = root
    }

    fun getRoot(): TreeNode? {
        return rootNode
    }

    fun getChildrenSize(): Int {
        return children.size
    }

    fun getChildren(): LinkedHashMap<String, TreeList> {
        return children
    }

    /**
     * Get-Set Method End
     */

    /**
     * Put the tree node into the tree structure
     * @param element tree node
     */
    fun put(element: TreeNode) {
        if (element.equals(rootNode)) return
        if (searchList(element.path) !== null) return

        if (element.level > getRootLevel() + 1) {
            //target root level = element level - 1
            val temp = searchList(element.parentPath as String)
            temp?.children?.put(element.path, TreeList(element))
            temp?.rootNode?.hasChildren = true
            temp?.isInitialized = true
        } else {
            //root's children
            val tempList = TreeList(element)
            children.put(element.path, tempList)
        }

        rootNode.hasChildren = true
    }

    /**
     * Put the name and path collections into the tree structure
     * @param nameList ArrayList of name
     * @param pathList Map of path
     * @param path destination path
     */
    fun putAll(nameList: ArrayList<String>, pathList: Map<String, String>, path: String = FileMan.ROOT) {

        /*nameList.forEach {
                    put(searchNodes(pathList[if (it=="/" || it=="/") it else it.split("/")[0]] as String)
                            ?: nodeFactory(
                                if (pathList["../"] !== null)
                                    searchNodes(path)!!
                                else
                                    rootNode
                            , it
                    )) }*/

        var count = 0
        for (name in nameList) {
            LogInstance.log("$this", (++count).toString(), LogInstance.LogType.DEBUG, showLog)
            val modName =
                    if (name == "/" || name == "../")
                        name
                    else
                        name.split("/")[0]

            val perTemp = pathList[modName] as String
            val temp = searchNodes(perTemp)

            if (temp !== null) {
                put(temp)
            } else {
                val tempPath = pathList["../"]
                put(nodeFactory(
                        if (tempPath !== null)
                            searchNodes(path)!!
                        else rootNode,
                        modName)
                )
            }
        }
    }

    /**
     * Get node by searching path
     * @param path target node path
     * @return TreeNode or null
     */
    fun get(path: String): TreeNode? {
        when (path) {
            getRootPath() -> return rootNode
            in children.keys -> return children[path]?.rootNode
            !in children.keys -> return searchNodes(path)
            else -> return null
        }
    }

    /***
     * For searching downstream for nodes level difference > 1
     *
     * Recursion is used with tailrec. OOM may not happen normally
     * @param path node path
     * @param list Map of children TreeList, DEFAULT: this.children
     */
    fun searchList(path: String, list: LinkedHashMap<String, TreeList> = children): TreeList? {
        if (rootNode.isEmpty) throw TreeNode.NullTreeNodeException()
        if (path == rootNode.path) return this

        /*TODO-Bug exist. Debug later
        list.forEach {
            if (it.key === path) return list[path]
            else {
                val temp = searchList(path, it.value.children)
                if (temp !== null) return temp
            }
        }
        return null*/

        var temp: TreeList? = null
        for ((key, value) in list) {
            if (key.contains(path)) {
                temp = value
                break
            } else {
                val recTemp = value.searchList(path, value.children)
                if (recTemp !== null) {
                    temp = recTemp
                    break
                }
            }
        }
        return temp
    }

    /**
     * Find the target tree node
     * @param path target path
     * @return target tree node or null
     */
    private fun searchNodes(path: String): TreeNode? {
        return searchList(path)?.rootNode
    }

    /**
     * Empty all list to ensure the resources occupied can be collected via GC
     */
    fun clear() {
        children.clear()
        rootNode = TreeNode()
    }

    /**
     * Generate path automatically
     * @param parent parent node
     * @param name dest name
     * @return path(String)
     */
    private fun pathFactory(parent: TreeNode, name: String): String {
        val buffer =
                StringBuffer()
                        .append(parent.path)
                        .append("/")
                        .append(name)
        return buffer.toString()
    }

    /**
     * TODO extract the Dropbox one by using inheritance eg. DropTreeList
     * Node Factory body
     * @param parent, can be null
     * @param name, can be null
     * @return tree node
     * @throws Exception null parent
     */
    private fun nodeFactoryBody(parent: TreeNode?, name: String?, isDropbox: Boolean = false, metadata: Metadata? = null): TreeNode {
        if (rootNode.isEmpty) {
            val node: TreeNode
            if (parent === null && name === null)
                node = TreeNode(TreeNode.PATH_TOP, TreeNode.NAME_TOP, TreeNode.LEVEL_TOP, null)
            else
                node = TreeNode(name!!, name, TreeNode.LEVEL_TOP, null)
            setRoot(node)
            return node
        } else if (parent !== null && name !== null) {
            val path = pathFactory(parent, name)
            if (!isDropbox) {
                val isDirectory = File(path).isDirectory
                return TreeNode(parent, path, if (isDirectory) name else name, isDirectory)
            } else {
                val isDir = metadata is FolderMetadata
                LogInstance.log("$this isFolder?", isDir.toString(), LogInstance.LogType.DEBUG, showLog)
                return TreeNode(parent, path, metadata?.name as String, isDir)
            }
        } else
            throw Exception("Check $this")
    }

    /**
     * Factory method to create tree node
     * @param parent parent node
     * @param name dest name
     */
    fun nodeFactory(parent: TreeNode, name: String): TreeNode {
        return nodeFactoryBody(parent, name)
    }

    /**
     * Factory method to create root tree node
     * <p>
     * Not suitable for Dropbox and External SD Card!
     */
    fun nodeFactory(): TreeNode {
        return nodeFactoryBody(null, null)
    }

    fun rootNodeFactory(rootDir: String): TreeNode {
        return nodeFactoryBody(null, rootDir)
    }

    /**
     * Dropbox metadata node factory
     */
    fun nodeFactory(parent: TreeNode, metadata: Metadata): TreeNode {
        return nodeFactoryBody(parent, metadata.name, true, metadata)
    }

    /**
     * For testing,
     * show the tree structure like this:
     * 0
     * +0-0
     * ++0-0-0
     * +0-1....
     **/
    @Deprecated("Just for testing. Never Use this function if not necessary!")
    fun showAllAsString(): String {
        val temp = StringBuffer()
        temp.append(getRootPath())

        for (i in children.values)
            temp.append(nodesToString(i))

        return temp.toString()
    }

    /**
     * Notes Listing (Testing Purpose)
     *
     * Warning: Recursion is used. OOM may occurred!
     * @param list source list
     * @see showAllAsString
     */
    private fun nodesToString(list: TreeList): String {
        val temp = StringBuffer()

        temp.append("\n")
        for (j in 1..list.rootNode.level) {
            temp.append("+")
        }
        temp.append(list.getRootPath())

        for (i in list.children.values) {
            temp.append(nodesToString(i))
        }

        return temp.toString()
    }

    /**
     * Extract specific directory list from tree, used in FileManActivity
     * @param path node path
     * @return destination directory in ArrayList, empty list will be returned if the directory does not contain anything
     */
    fun toArrayList(path: String, root: String = FileMan.ROOT): ArrayList<String> {
        val temp = ArrayList<String>()
        val destList = searchList(path) as TreeList

        if (destList.getRootPath() != root) {
            temp.add("/")
            temp.add("../")
        }

        if (!destList.rootNode.hasChildren)
            return temp
        else
            temp.addAll(destList.children.map { if (it.value.rootNode.isDirectory) it.value.rootNode.name + "/" else it.value.rootNode.name })

        return temp
    }

    /**
     * Extract specific directory map from tree
     * @param path node path
     * @return destination directory map in LinkedHashMap
     */
    fun toMap(path: String): LinkedHashMap<String, String> {
        val temp = LinkedHashMap<String, String>()
        val destList = toArrayList(path)
        val parent = searchNodes(path)

        temp.putAll(destList.associateBy({it}, {pathFactory(parent as TreeNode, it)}))

        return temp
    }

}